package com.cy.pj.log.aspect;

import com.cy.pj.log.annotation.RequiredLog;
import com.cy.pj.log.pojo.SysLog;
import com.cy.pj.log.service.AsyncLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 在Spring AOP应用中，基于@Aspect注解描述的
 * 类型为一个切面类型，此类中要封装切入点及通知方法的定义
 * 1)切入点:要切入扩展业务逻辑的一些目标方法集合(例如使用了特点注解描述的方法)
 * 2)通知：封装了扩展业务逻辑的一个方法(spring中也可使用特定注解进行描述)
 */
@Order(2)
@Slf4j
@Aspect
@Component
public class SysLogAspect {

//    private static final Logger log=
//            LoggerFactory.getLogger(SysLogAspect.class);

    /**
     * 基于@Pointcut注解定义切入点，这里的@annotation为一种切入点表达式，
     * 表示由RequiredLog注解描述的方法为一个切入点方法，我们要在这样的
     * 方法上添加扩展业务逻辑
     */
    @Pointcut("@annotation(com.cy.pj.log.annotation.RequiredLog)")
    public void doLog() {
    }//此方法用于承载切入点的定义

    /**
     * 定义一个通知方法，这里使用@Around进行描述，表示在此方法内部可以调用
     * 目标方法，可以在目标方法执行之前或之后做一些拓展业务
     *
     * @param jp 连接点，用于封装要执行的目标切入点方法信息，
     *           ProceedingJoinPoint此类型参数只能定义在，@Around注解描述的方法中
     * @return 这里的返回值一般为目标切入点方法的执行结果
     * @throws Throwable
     */
    //@Around("@annotation(com.cy.pj.common.annotation.RequiredLog)")
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("SysLogAspect.@Around.before");
        long t1 = System.currentTimeMillis();
        log.info("method start {}", t1);
        try {
            //String targetCls=jp.getTarget().getClass().getName();
            //System.out.println("targetCls="+targetCls);
            Object result = jp.proceed();//通过连接点调用目标方法(method.invoke)
            long t2 = System.currentTimeMillis();
            log.info("method after {}", t2);
            //记录用户正常行为日志
            doSaveLogInfo(jp, (t2 - t1), null);//springboot池中的线程
            return result;
        } catch (Throwable e) {
            log.error("method error {}", System.currentTimeMillis());
            long t3 = System.currentTimeMillis();
            //记录用户异常行为日志
            doSaveLogInfo(jp, (t3 - t1), e);
            throw e;
        }
    }

    @Autowired
    private AsyncLogService sysLogService;

    private void doSaveLogInfo(ProceedingJoinPoint jp, long time, Throwable e) throws NoSuchMethodException, JsonProcessingException {
        //1.获取用户行为日志
        //1.1获取登录用户信息
        String username = "tony";//后续是系统的登录用户
        //1.2获取登录用户的ip地址
        String ip = "192.168.100.11";//后续可以借助三方工具类
        //1.3获取用户操作
        //1.3.1获取方法所在类的字节码对象(目标对象对应的字节码对象)
        Class<?> cls = jp.getTarget().getClass();
        //1.3.2获取注解描述的方法对象(字节码对象，方法名，参数列表)
        Signature signature = jp.getSignature();
        // System.out.println("signature="+signature.getClass().getName());//MethodSignatureImpl
        //  MethodSignatureImpl
        MethodSignature methodSignature = (MethodSignature) signature;
        //Method targetMethod=methodSignature.getMethod();//cglib
        Method targetMethod =//cglib,jdk
                cls.getDeclaredMethod(methodSignature.getName(),
                        methodSignature.getParameterTypes());
        //System.out.println("targetMethod="+targetMethod);
        //1.3.3获取RequiredLog注解
        RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class);
        String operation = requiredLog.operation();
        //1.4获取方法声明信息
        String method = cls.getName() + "." + targetMethod.getName();
        //1.5获取方法执行时传入的实际参数
        Object[] args = jp.getArgs();//实际参数
        String params = new ObjectMapper().writeValueAsString(args);//转json
        //1.6获取状态信息
        Integer status = e == null ? 1 : 0;//1表示ok
        //1.7获取错误信息
        String error = e == null ? "" : e.getMessage();
        //2.封装用户行为日志
        SysLog sysLog = new SysLog();
        sysLog.setUsername(username);
        sysLog.setIp(ip);
        sysLog.setCreatedTime(new Date());
        sysLog.setOperation(operation);
        sysLog.setMethod(method);
        sysLog.setParams(params);
        sysLog.setStatus(status);
        sysLog.setError(error);
        sysLog.setTime(time);
        //将日志写入到数据库
        log.info("user log {}", new ObjectMapper().writeValueAsString(sysLog));
        sysLogService.saveLog(sysLog);
//       new Thread() {//1M
//             @Override
//             public void run() {
//                sysLogService.saveLog(sysLog);
//             }
//       }.start();

    }


}
